package com.briup.pai.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.briup.pai.common.constant.AuthConstant;
import com.briup.pai.common.constant.LoginConstant;
import com.briup.pai.common.enums.ResultCodeEnum;
import com.briup.pai.common.enums.UserStatusEnum;
import com.briup.pai.common.exception.BriupAssert;
import com.briup.pai.common.exception.CustomException;
import com.briup.pai.common.utils.SecurityUtil;
import com.briup.pai.convert.UserConvert;
import com.briup.pai.dao.UserMapper;
import com.briup.pai.entity.dto.UserSaveDTO;
import com.briup.pai.entity.po.User;
import com.briup.pai.entity.po.UserRole;
import com.briup.pai.entity.vo.PageVO;
import com.briup.pai.entity.vo.UserEchoVO;
import com.briup.pai.entity.vo.UserPageVO;
import com.briup.pai.service.IUserRoleService;
import com.briup.pai.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;

@Service
@CacheConfig(cacheNames = AuthConstant.USER_CACHE_PREFIX)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Value("${upload.nginx-file-path}")
    private String nginxFilePath;
    @Value("${upload.nginx-server}")
    private String nginxServer;

    @Autowired
    private UserConvert userConvert;

    @Autowired
    private IUserRoleService userRoleService;

    @Override
    @Cacheable(key = "T(com.briup.pai.common.constant.AuthConstant).CREATE_USERNAME_CACHE_PREFIX+':'+#userId",
               unless = "#result == ''")
    public String getUsernameById(Integer userId) {
        User user = this.getById(userId);
        return ObjectUtil.isNotNull(user)?user.getUsername():"";
    }

    @Override
    public PageVO<UserPageVO> getUserByPageAndCondition(Long pageNum, Long pageSize, String keyword) {

        IPage<User> page = new Page<>(pageNum,pageSize);
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.like(ObjectUtil.isNotEmpty(keyword),User::getUsername,keyword)
                .or()
                .like(ObjectUtil.isNotEmpty(keyword),User::getRealName,keyword)
                .or()
                .like(ObjectUtil.isNotEmpty(keyword),User::getTelephone,keyword);
        IPage<User> userIPage = this.page(page, lqw);
        PageVO<UserPageVO> userPageVOPageVO = new PageVO<>();
        userPageVOPageVO.setTotal(userIPage.getTotal());
        userPageVOPageVO.setData(userConvert.po2UserPageVOList(userIPage.getRecords()));
        return userPageVOPageVO;
    }

    @Override
    @Transactional
    @CachePut(key = "#result.userId")
    public UserEchoVO addOrModifyUser(UserSaveDTO dto) {
        Integer userId = dto.getUserId();
        User user;
        if(ObjectUtil.isEmpty(userId)){
            // 新增
            BriupAssert.requireNull(
                    this.getById(userId),
                    ResultCodeEnum.DATA_ALREADY_EXIST);
            user = userConvert.userSaveDTO2Po(dto);
            // 放一些基础数据
            // 密码要加密。看一些常量
            user.setPassword(DigestUtils.md5DigestAsHex(LoginConstant.INIT_PASSWORD.getBytes(StandardCharsets.UTF_8)));
            user.setHeaderUrl(LoginConstant.INIT_HEADER_URL);
            user.setStatus(UserStatusEnum.AVAILABLE.getStatus());
            user.setCreateTime(new Date());
            this.save(user);
        }else{
            // 更新
            // 用户必须存在
            BriupAssert.requireNotNull(
                    this,
                    User::getUsername,
                    dto.getUsername(),
                    ResultCodeEnum.DATA_NOT_EXIST);
            // 必须改的是自己的
            BriupAssert.requireEqual(
                    SecurityUtil.getUserId(),
                    userId,
                    ResultCodeEnum.PARAM_IS_ERROR);
            // 用户名不能重复
            BriupAssert.requireNull(
                    this,
                    User::getUsername,
                    dto.getUsername(),
                    User::getId,
                    userId,
                    ResultCodeEnum.DATA_ALREADY_EXIST);
            user = userConvert.userSaveDTO2Po(dto);
            this.updateById(user);
        }
        return userConvert.po2UserEchoVO(user);
    }

    @Override
    public String uploadProfilePicture(MultipartFile file) {
        // 上传文件
        // 验证文件类型
        BriupAssert.requirePic(file);
        // 重新设置文件名字
        String originalFilename = file.getOriginalFilename();
        // 使用uuid拼接文件名字
        assert originalFilename != null;
        String newFilename = IdUtil.simpleUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
        File targetFile = new File(nginxFilePath+"/"+newFilename);
        // 保存文件
        try {
            file.transferTo(targetFile);
        } catch (IOException e) {
            throw new CustomException(ResultCodeEnum.FILE_UPLOAD_ERROR);
        }
        // 返回文件url
        return nginxServer+"/"+newFilename;
    }

    @Override
    @Cacheable(key = "#userId")
    public UserEchoVO getUserById(Integer userId) {
        return userConvert.po2UserEchoVO(BriupAssert.requireNotNull(
                this,
                User::getId,
                userId,
                ResultCodeEnum.DATA_NOT_EXIST
        ));
    }

    @Override
    @Transactional
    public void resetPassword(Integer userId) {
        User user = BriupAssert.requireNotNull(
                this,
                User::getId,
                userId,
                ResultCodeEnum.DATA_NOT_EXIST
        );
        user.setPassword(DigestUtils.md5DigestAsHex(LoginConstant.INIT_PASSWORD.getBytes(StandardCharsets.UTF_8)));
        this.updateById(user);
    }

    @Override
    @Transactional
    // 删除的缓存比较多，和role和user相关的缓存都要删掉
    @Caching(evict = {
            @CacheEvict(key = "#userId"),
            @CacheEvict(key = "T(com.briup.pai.common.constant.AuthConstant).CREATE_USERNAME_CACHE_PREFIX+':'+#userId"),
            @CacheEvict(key = "T(com.briup.pai.common.constant.AuthConstant).USER_ROLE_CACHE_PREFIX+':'+#userId"),
            @CacheEvict(key = "T(com.briup.pai.common.constant.AuthConstant).ROUTER_CACHE_PREFIX+':'+#userId"),
            @CacheEvict(key = "T(com.briup.pai.common.constant.AuthConstant).BUTTON_CACHE_PREFIX + ':'+ #userId")
    })
    public void removeUserById(Integer userId) {
        // 不能删除自己的用户
        BriupAssert.requireNotEqual(
                userId,
                SecurityUtil.getUserId(),
                ResultCodeEnum.DATA_CAN_NOT_DELETE
        );
        BriupAssert.requireNotNull(
                this,
                User::getId,
                userId,
                ResultCodeEnum.DATA_NOT_EXIST
        );
        // 删除用户之前，删除其关联的所有角色关系
        LambdaQueryWrapper<UserRole> lqw = new LambdaQueryWrapper<>();
        lqw.eq(UserRole::getUserId,userId);
        userRoleService.remove(lqw);
        // 删除用户
        removeById(userId);
    }

    @Override
    @Transactional
    public void disableOrEnableUser(Integer userId, Integer status) {
        User user = BriupAssert.requireNotNull(
                this,
                User::getId,
                userId,
                ResultCodeEnum.DATA_NOT_EXIST
        );
        // 不能自己改自己
        BriupAssert.requireNotEqual(
                userId,
                SecurityUtil.getUserId(),
                ResultCodeEnum.DISABLE_OR_ENABLE_ERROR
        );
        // status必须是enum里的值
        BriupAssert.requireIn(
                status,
                UserStatusEnum.statusList(),
                ResultCodeEnum.PARAM_IS_ERROR
        );
        // 修改的状态和原状态不能一样
        BriupAssert.requireNotEqual(
                status,
                user.getStatus(),
                ResultCodeEnum.PARAM_IS_ERROR
        );

        user.setStatus(status);
        updateById(user);
    }
}